﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Noris.Tools.FrxEditor.Data
{
    /// <summary>
    /// Collection : kolekce dat včetně funkcionality.
    /// Potomek musí přepsat abstraktní metodu GetName(), může využít virtuální metodu CheckItemBeforeAdd().
    /// Může definovat StringComparison pro jméno - v konstruktoru base(StringComparison).
    /// </summary>
    public abstract class Collection<T> : IEnumerable<T>
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        public Collection()
        {
            this._Items = new List<T>();
            this.NameComparison = StringComparison.Ordinal;
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="nameComparison">Režim pro vyhledání položky dle názvu. Default = Ordinal.</param>
        public Collection(StringComparison nameComparison)
        {
            this._Items = new List<T>();
            this.NameComparison = nameComparison;
        }
        /// <summary>
        /// Pole položek
        /// </summary>
        protected List<T> _Items;
        /// <summary>
        /// Režim vyhledání podle jména
        /// </summary>
        protected StringComparison NameComparison = StringComparison.Ordinal;
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Collection of " + typeof(T).Name + ": " + this.Count.ToString() + " items";
        }
        #endregion
        #region Abstract a virtual
        /// <summary>
        /// Abstraktní metoda kolekce, která vrátí klíčový název z dané položky
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        protected abstract string GetName(T item);
        /// <summary>
        /// Prostor pro metodu na potomkovi, která prověří korektnost položky dat před jejím přidáním do kolekce.
        /// Metoda nemá za úkol prověřovat duplicitu podle názvu, to proběhne automaticky následovně.
        /// </summary>
        /// <param name="item"></param>
        protected virtual void CheckItemBeforeAdd(T item) { }
        #endregion
        #region Public property
        /// <summary>
        ///Jednotlivé položky
        /// </summary>
        public IEnumerable<T> Items { get { return this._Items; } }
        /// <summary>
        /// Příznak, zda je kolekce prázdná
        /// </summary>
        public bool IsEmpty { get { return (this._Items.Count == 0); } }
        /// <summary>
        /// Počet položek v kolekci
        /// </summary>
        public int Count { get { return this._Items.Count; } }
        /// <summary>
        /// Vrátí položku z daného indexu.
        /// Index mimo rozsah vyhodí chybu.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T this[int index]
        {
            get { return this._Items[index]; }
        }
        /// <summary>
        /// Vrátí položku daného názvu.
        /// Neexistující název vyhodí chybu.
        /// Porovnání názvu probíhá podle StringComparison zadaného v konstruktoru, default = Ordinal.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public T this[string name]
        {
            get { return this.FindByName(name); }
        }
        #endregion
        #region Vyhledání podle názvu
        /// <summary>
        /// Najde a vrátí položku daného jména.
        /// Pokud nenajde, vyhodí chybu.
        /// Porovnání názvu probíhá podle StringComparison zadaného v konstruktoru, default = Ordinal.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public T FindByName(string name)
        {
            T param;
            if (!this.TryFindByName(name, out param))
                throw new FrxException("Položka jména " + name + " v seznamu neexistuje.");
            return param;
        }
        /// <summary>
        /// Vrátí true, pokud kolekce obsahuje položku daného jména
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool ContainsName(string name)
        {
            T item;
            return this.TryFindByName(name, out item);
        }
        /// <summary>
        /// Zkusí najít položku daného jména.
        /// Vrací true = existuje. Hodnotu dává do out parametru.
        /// Porovnání názvu probíhá podle StringComparison zadaného v konstruktoru, default = Ordinal.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool TryFindByName(string name, out T item)
        {
            item = this._Items.FirstOrDefault(p => String.Equals(GetName(p), name, this.NameComparison));
            return (item != null);
        }
        #endregion
        #region Přidávání a nulování položek
        /// <summary>
        /// Přidá další položku do kolekce.
        /// </summary>
        /// <param name="item"></param>
        public void Add(T item)
        {
            this._ItemsAdd(item);
        }
        /// <summary>
        /// Přidá další položky do kolekce.
        /// </summary>
        /// <param name="items"></param>
        public void AddRange(IEnumerable<T> items)
        {
            if (items == null) return;
            foreach (T item in items)
                this._ItemsAdd(item);
        }
        /// <summary>
        /// Import položek z dodaného zdroje.
        /// Import nejprve smaže všechny stávající položky, a pak do sebe přidá položky dodané.
        /// </summary>
        /// <param name="items"></param>
        public void Import(IEnumerable<T> items)
        {
            this.Clear();
            if (items == null) return;
            foreach (T item in items)
                this._ItemsAdd(item);
        }
        /// <summary>
        /// Odebere dané položky.
        /// Dané položky musí být tytéž objekty, které jsou uloženy v kolekci (shoda položek se určuje metodou Object.ReferenceEquals()).
        /// Tuto metodu tedy není možno použít v případě položky typu structure (Value type).
        /// </summary>
        /// <param name="items"></param>
        public void Remove(IEnumerable<T> items)
        {
            if (items == null) return;
            foreach (T item in items)
                this.Remove(item);
        }
        /// <summary>
        /// Odebere danou položku.
        /// Daná položky musí být identický objekt, který je uložen v kolekci (shoda položek se určuje metodou Object.ReferenceEquals()).
        /// Tuto metodu tedy není možno použít v případě položky typu structure (Value type).
        /// </summary>
        /// <param name="item"></param>
        public void Remove(T item)
        {
            this._Items.RemoveAll(i => Object.ReferenceEquals(i, item));
        }
        /// <summary>
        /// Odebere položku na uveeném indexu.
        /// </summary>
        /// <param name="index"></param>
        public void RemoveAt(int index)
        {
            this._Items.RemoveAt(index);
        }
        /// <summary>
        /// Smaže všechny položky.
        /// </summary>
        public void Clear()
        {
            this._Items.Clear();
        }
        /// <summary>
        /// Přidá další parametr
        /// </summary>
        /// <param name="item"></param>
        private void _ItemsAdd(T item)
        {
            this.CheckItemBeforeAdd(item);
            string name = this.GetName(item);
            T duplicity;
            if (this.TryFindByName(name, out duplicity))
                throw new FrxException("Položka jménem " + name + " je uvedena více než jednou.");

            this._Items.Add(item);
        }
        #endregion
        #region IEnumerable
        IEnumerator<T> IEnumerable<T>.GetEnumerator() { return this._Items.GetEnumerator(); }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this._Items.GetEnumerator(); }
        #endregion
    }
}
